import oss2
import requests
import os
from fastapi import HTTPException
from config import OSS_ENDPOINT, OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET, OSS_BUCKET_NAME, OSS_CDN_DOMAIN_NAME
from loguru import logger
from retrying import retry

class OSSClient:
    def __init__(self, oss_endpoint, access_key_id, access_key_secret, bucket_name, oss_cnd_domain_name,
                 base_directory=None):
        """
        初始化OSS客户端

        :param oss_endpoint: OSS的Endpoint
        :param access_key_id: 阿里云的Access Key ID
        :param access_key_secret: 阿里云的Access Key Secret
        :param bucket_name: 存储空间的名称
        :param base_directory: OSS中的基础目录路径（可选）
        """
        self.oss_endpoint = oss_endpoint
        self.access_key_id = access_key_id
        self.access_key_secret = access_key_secret
        self.bucket_name = bucket_name
        self.base_directory = base_directory
        self.oss_cdn_domain_name = oss_cnd_domain_name

        # 创建OSS认证
        self.auth = oss2.Auth(self.access_key_id, self.access_key_secret)

        # 创建Bucket对象
        self.bucket = oss2.Bucket(self.auth, self.oss_endpoint, self.bucket_name)

    def _build_object_name(self, object_name, directory):
        """
        构建OSS中的对象名称

        :param object_name: 文件名
        :param directory: OSS中的目录路径
        :return: 构建后的完整路径
        """
        if directory:
            object_name = os.path.join(directory, object_name).replace("\\", "/")
        if self.base_directory:
            object_name = os.path.join(self.base_directory, object_name).replace("\\", "/")
        return object_name
    @retry(
        stop_max_attempt_number=3,  # 最大重试3次
        wait_exponential_multiplier=1000,  # 重试等待时间以指数增长，初始1秒
        wait_exponential_max=10000,  # 最大等待10秒
        retry_on_exception=lambda e: isinstance(e, (ConnectionError, TimeoutError))
        )
    def upload_file(self, local_file_path, object_name=None, directory=None):
        """
        上传本地文件到OSS的指定目录
        """
        try:
            if not object_name:
                object_name = os.path.basename(local_file_path)
            object_name = self._build_object_name(object_name, directory)

            # 设置超时时间和重试次数
            self.bucket.timeout = 30  # 30秒超时
            
            # 检查文件是否存在且可读
            if not os.path.exists(local_file_path):
                raise FileNotFoundError(f"文件不存在: {local_file_path}")
            
            # 检查文件大小
            file_size = os.path.getsize(local_file_path)
            if file_size == 0:
                raise ValueError(f"文件大小为0: {local_file_path}")

            # 上传文件
            result = self.bucket.put_object_from_file(object_name, local_file_path)
            
            # 验证上传是否成功
            if result.status != 200:
                raise Exception(f"上传失败，状态码: {result.status}")
                
            logger.debug(f"文件已成功上传到OSS: {object_name}")
            return result

        except Exception as e:
            logger.error(f"上传过程中出现错误: {str(e)}, 详细信息: {e.args}")
            logger.error(f"object_name: {object_name}, local_file_path: {local_file_path}")
            # 如果是网络错误，则抛出以便重试
            if isinstance(e, (ConnectionError, TimeoutError)):
                raise
            raise HTTPException(status_code=502, detail=f"上传文件时发生问题: {str(e)}")

    def get_file_url(self, object_name, directory=None):
        """
        获取文件在OSS中的访问URL

        :param object_name: 文件在OSS中的路径（包括文件名）
        :param directory: OSS中的目录路径（可选）
        :return: 文件的URL
        """
        try:
            object_name = self._build_object_name(object_name, directory)
            file_url = f"https://{self.oss_cdn_domain_name.replace('http://', '').replace('https://', '')}/{object_name}"
            logger.debug(f"试听音频文件URL: {file_url}")
            return file_url

        except Exception as e:
            logger.error(f"获取文件URL时出现错误: {str(e)}")
            raise HTTPException(status_code=502, detail="获取文件URL时发生了问题。")

    def download_file(self, object_name, local_file_path, directory=None):
        """
        从OSS下载文件到本地

        :param object_name: 要下载的文件在OSS中的路径（包括文件名）
        :param local_file_path: 下载到本地的文件路径（包括文件名）
        :param directory: OSS中的目录路径（可选）
        :return: 下载结果
        """
        try:
            object_name = self._build_object_name(object_name, directory)
            self.bucket.get_object_to_file(object_name, local_file_path)
            logger.debug(f"文件已成功下载到: {local_file_path}")

        except oss2.exceptions.NoSuchKey:
            logger.error("文件不存在")

        except Exception as e:
            logger.error(f"下载过程中出现错误: {str(e)}")

    def delete_file(self, object_name, directory=None):
        """
        删除OSS中的文件

        :param object_name: 要删除的文件在OSS中的路径（包括文件名）
        :param directory: OSS中的目录路径（可选）
        :return: 删除结果
        """
        try:
            object_name = self._build_object_name(object_name, directory)
            result = self.bucket.delete_object(object_name)
            logger.info(f"文件已成功删除: {object_name}")
            return result

        except Exception as e:
            logger.error(f"删除过程中出现错误: {str(e)}")
            return None

    def list_files(self, prefix='', directory=None, max_keys=100):
        """
        列出OSS中的文件

        :param prefix: 需要列出的文件的前缀
        :param directory: OSS中的目录路径（可选）
        :param max_keys: 最大列出文件数
        :return: 文件列表
        """
        try:
            if directory:
                prefix = os.path.join(directory, prefix).replace("\\", "/")
            if self.base_directory:
                prefix = os.path.join(self.base_directory, prefix).replace("\\", "/")

            # 列出文件
            file_list = self.bucket.list_objects(prefix=prefix, max_keys=max_keys)
            for obj in file_list.object_list:
                logger.info(f"文件名: {obj.key}")
            return file_list.object_list

        except Exception as e:
            logger.error(f"列出文件过程中出现错误: {str(e)}")
            return None

    @staticmethod
    def check_url_validity(url):
        """
        检查URL是否有效

        :param url: 需要检查的URL
        :return: 如果有效返回True，否则返回False
        """
        try:
            response = requests.head(url)
            if response.status_code == 200:
                return True
            else:
                logger.warning(f"URL无效，状态码：{response.status_code}，URL：{url}")
                return False
        except requests.RequestException as e:
            logger.error(f"请求URL时出现错误：{e}")
            return False


oss_client = OSSClient(OSS_ENDPOINT, OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET, OSS_BUCKET_NAME, OSS_CDN_DOMAIN_NAME)


